package shardkv

import (
	"sync"
	"time"
)

func (kv *ShardKV) notifyDone(command *ShardCommand) {
	kv.logger.Printf("notifyDone: 开始通知上一个分组清楚旧分片，分片ID=%d，配置号=%d", command.Sid, command.ConfigNum)
	//配置已经过期了，直接忽略
	if kv.curConfig.Num != command.ConfigNum {
		kv.logger.Printf("notifyDone: 配置号不匹配（当前=%d, 命令=%d），忽略操作，分片ID=%d", kv.curConfig.Num, command.ConfigNum, command.Sid)
		return
	}
	//如果不是等待通知，说明已经通知过了，直接忽略
	if kv.Shards[command.Sid].State != NeedNotify {
		kv.logger.Printf("notifyDone: 分片状态不是NeedNotify（当前=%v），忽略操作，分片ID=%d", kv.Shards[command.Sid].State, command.Sid)
		return
	}
	//直接赋值
	kv.Shards[command.Sid].State = Ready
	kv.logger.Printf("notifyDone: 成功更新分片状态为Ready，分片ID=%d，配置号=%d", command.Sid, command.ConfigNum)
}

//定期检查需要通知对方的切片，通知对方删除历史分片
func (kv *ShardKV) notifyShard() {
	for !kv.Killed() {
		if term, isLeader := kv.rf.GetState(); isLeader {
			kv.mu.RLock()
			kv.logger.Printf("notifyShard: 当前为Leader（任期=%d），开始检查需要通知的分片", term)
			startNum := kv.curConfig.Num
			prevConfig := kv.prevConfig
			kv.logger.Printf("notifyShard: 当前配置号=%d，开始处理基于前序配置的分片通知", startNum)
			var wg sync.WaitGroup
			//用旧版本是因为需要获取上个分组的地址
			for sid, gid := range prevConfig.Shards {

				//不需要通知
				if kv.Shards[sid].State != NeedNotify {
					//kv.logger.Printf("notifyShard: 分片ID=%d 状态不是NeedNotify（当前=%v），无需通知", sid, kv.Shards[sid].State)
					continue
				}
				kv.logger.Printf("notifyShard: 分片ID=%d 需要通知删除，目标GID=%d", sid, gid)
				wg.Add(1)
				go func(sid, src, startNum int, addrs []string) {
					defer wg.Done()
					index := 0
					args := ShardArgs{
						Type:      "NotifyDelete",
						ShardId:   sid,
						ConfigNum: startNum,
					}
					kv.logger.Printf("notifyShard: 启动通知协程，分片ID=%d，目标地址列表=%v", sid, addrs)
					//轮询所有节点找到leader通信
					for {
						//如果节点状态变化过，最好放弃本次拉取，重新拉取
						if kv.rf.StateChanged(term) {
							kv.logger.Printf("notifyShard: 任期变化或不再是Leader，停止通知分片ID=%d", sid)
							return
						}
						kv.mu.RLock()
						curNum := kv.curConfig.Num
						kv.mu.RUnlock()
						//配置已经更新了，直接退出，不应该修改其他配置版本的分片状态（之前提交的命令应用成功了）
						if curNum != startNum {
							kv.logger.Printf("notifyShard: 配置号已更新（当前=%d, 开始=%d），停止通知分片ID=%d",
								curNum, startNum, sid)
							return
						}
						kv.logger.Printf("notifyShard: 尝试通知删除分片ID=%d，（索引=%d）",
							sid, index)
						reply := ShardReply{}
						end := kv.make_end(addrs[index])
						//请求对方删除分片
						ok := end.Call("ShardKV.HandleShardRequests", &args, &reply)
						//请求成功
						if ok {
							if reply.Err == OK {
								kv.logger.Printf("notifyShard: 成功通知删除分片ID=%d", sid)
								command := ShardCommand{
									Type:      "notifyDone",
									Sid:       sid,
									ConfigNum: startNum,
								}
								//提交到raft
								kv.rf.Start(command)
								kv.logger.Printf("notifyShard: 分片ID=%d的通知完成命令已提交到Raft", sid)
								//虽然提交但是日志可能丢失，所以不能立即修改状态
								return
							} else if reply.Err == ErrDuplicate {
								kv.logger.Printf("notifyShard: 分片ID=%d已处理过删除通知（ErrDuplicate），可能需要修改分片状态", sid)
								//对方已经处理过删除了，只是自己没收到消息，所以可以检查状态，让自己进入下一个阶段。也不用加锁，直接提交命令，因为到时候会检查也是一样的
								//不过加个读锁可以提前检查一下
								kv.mu.RLock()
								command := ShardCommand{
									Type:      "notifyDone",
									Sid:       sid,
									ConfigNum: startNum,
								}
								//提交到raft
								kv.rf.Start(command)
								kv.logger.Printf("notifyShard: 分片ID=%d已处理过删除通知（ErrDuplicate），重新提交notifyDone命令", sid)
								kv.mu.RUnlock()
								return
							}
						} else {
							kv.logger.Printf("notifyShard: 调用失败，分片ID=%d", sid)
						}
						//尝试下一个节点
						index = (index + 1) % len(addrs)
					}
				}(sid, gid, startNum, prevConfig.Groups[gid])
			}
			//先释放锁再等待，这样中间完成的分片可以被临时使用用于完成挑战2
			kv.mu.RUnlock()
			kv.logger.Printf("notifyShard: 等待所有通知协程完成，配置号=%d", startNum)
			//等待所有分片拉取结束，才可能进入下一个拉取循环，然后等待100ms，日志应当已经应用，若未应用说明可能丢失了
			wg.Wait()
			kv.logger.Printf("notifyShard: 本轮通知协程全部完成，配置号=%d", startNum)
		}
		time.Sleep(PullShardTimeout)
	}
}
